/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package Control;

/**
 *
 * @author DRAIKO
 */

public class HoltWintersMultiplicativeForecasting implements IForecasting{

    /*Implementation of the algorithm:
     * y : array of data
     * alpha, beta and gama: ramdom constants
     * (not implemented here their estimation)
     * 
     * the largest period and m to reach the maximun of predictions
     */
    
    public static Double[] forecast(Double[] y, double alpha, double beta,
            double gamma, int period, int m) {

        int seasons = y.length / period;
        double a0 = calculateInitialLevel(y);
        double b0 = calculateInitialTrend(y, period);
        Double[] initialSeasonalIndices = calculateSeasonalIndices(y, period,
                seasons);

        Double[] forecast = calculateHoltWinters(y, a0, b0, alpha, beta, gamma,
                initialSeasonalIndices, period, m);

        return forecast;
    }

    private static Double[] calculateHoltWinters(Double[] y, double a0, double b0,
            double alpha, double beta, double gamma,
            Double[] initialSeasonalIndices, int period, int m) {

        double[] St = new double[y.length];
        double[] bt = new double[y.length];
        double[] It = new double[y.length];
        Double[] Ft = new Double[y.length + m];

        // Initialize base values
        St[1] = a0;
        bt[1] = b0;

        for (int i = 0; i < period; i++) {
            It[i] = initialSeasonalIndices[i];
        }

        // Start calculations
        for (int i = 2; i < y.length; i++) {

            // Calculate overall smoothing
            if ((i - period) >= 0) {
                St[i] = alpha * y[i] / It[i - period] + (1.0 - alpha)
                        * (St[i - 1] + bt[i - 1]);
            } else {
                St[i] = alpha * y[i] + (1.0 - alpha) * (St[i - 1] + bt[i - 1]);
            }

            // Calculate trend smoothing
            bt[i] = gamma * (St[i] - St[i - 1]) + (1 - gamma) * bt[i - 1];

            // Calculate seasonal smoothing
            if ((i - period) >= 0) {
                It[i] = beta * y[i] / St[i] + (1.0 - beta) * It[i - period];
            }

            // Calculate forecast
            if (((i + m) >= period)) {
                Ft[i + m] = (St[i] + (m * bt[i])) * It[i - period + m];
            }
        }

        return Ft;
    }

 
    private static double calculateInitialLevel(Double[] y) {
        return y[0];
    }

    private static double calculateInitialTrend(Double[] y, int period) {

        double sum = 0;

        for (int i = 0; i < period; i++) {
            sum += (y[period + i] - y[i]);
        }

        return sum / (period * period);
    }

    private static Double[] calculateSeasonalIndices(Double[] y, int period,
            int seasons) {

        double[] seasonalAverage = new double[seasons];
        Double[] seasonalIndices = new Double[period];
        
        for (int i = 0; i < period; i++) {
            seasonalIndices[i] = new Double(0);
        }

        double[] averagedObservations = new double[y.length];

        for (int i = 0; i < seasons; i++) {
            for (int j = 0; j < period; j++) {
                seasonalAverage[i] += y[(i * period) + j];
            }
            seasonalAverage[i] /= period;
        }

        for (int i = 0; i < seasons; i++) {
            for (int j = 0; j < period; j++) {
                averagedObservations[(i * period) + j] = y[(i * period) + j]
                        / seasonalAverage[i];
            }
        }

        for (int i = 0; i < period; i++) {
            for (int j = 0; j < seasons; j++) {
                seasonalIndices[i] += averagedObservations[(j * period) + i];
            }
            seasonalIndices[i] /= seasons;
        }

        return seasonalIndices;
    }

    @Override
    public Double[] forecast(Double[] inputs) throws IllegalArgumentException {
        
        if(!Validator.validate(inputs))
            throw new IllegalArgumentException("The inputs' number must be equal or greater than 5.");
        
        //Random values to alpha, beta and gama
        double alpha = 0.15;
        double beta = 0.57;
        double gamma = 0.65;
        int period = inputs.length/2;
        int m = inputs.length/2;

        Double[] holtWinter = forecast(inputs, alpha, beta, gamma, period, m);
        
        Double[] outputs = new Double[inputs.length];
        
        for (int i = 0; i < outputs.length ; i++) {
            if(holtWinter[holtWinter.length - 1 - i] != null)
                outputs[outputs.length - 1 - i] = holtWinter[holtWinter.length - 1 - i];
            else
                outputs[outputs.length - 1 - i] = inputs[inputs.length - 1 - i];
        }
        
        return outputs;
    }

}
